// Protected namespace.
var jsguide = {};

// The Google Data Calendar service object, used for AuthSub.
jsguide.calendarService = null;

// The current action use case that the user is executing.
jsguide.currentActionId = null;

// CSS IDs.
jsguide.MAIN = 'main';
jsguide.DISPLAY = 'display';
jsguide.LOGIN_BUTTON = 'login';
jsguide.ACTION_SECTION = 'action';
jsguide.TAB_SECTION = 'tabsection';
jsguide.TAB_TEMPLATE = 'tabtemplate';
jsguide.ANCHOR = 'anchor';
jsguide.ACTION_TEXT = 'actiontext';
jsguide.CODELINK = 'codelink';
jsguide.RUNCODE_BUTTON = 'runcode';
jsguide.RERUNCODE_BUTTON = 'reruncode';
jsguide.EDITCODE_BUTTON = 'editcode';
jsguide.RESETCODE_BUTTON = 'resetcode';
jsguide.SOURCECODE = 'sourcecode';
jsguide.OVERVIEW = 'overview';
jsguide.TAB = 'tab';
jsguide.VIEWCALENDAR = 'viewcalendar';
jsguide.CALENDARCHOOSER = 'calchooser';
jsguide.EMBED_CALENDAR = 'embedcal';
jsguide.CALENDAR_SECTION = 'calsection';
jsguide.EMBED_CALENDAR_HOLDER = 'embedcalholder';

// Constant estimate of line height in Codepress, about 18px per line.
jsguide.PX_PER_LINE = 18;

// Constant URLs.
jsguide.SCOPE_URL = 'http://www.google.com/calendar/feeds/';
jsguide.ALL_CALENDAR_FEED = 
    'http://www.google.com/calendar/feeds/default/allcalendars/full';
jsguide.EMBED_CALENDAR_URL = 
    'http://www.google.com/calendar/embed?src=';
jsguide.PARENT_URL = 'http://code.google.com/apis/calendar/' + 
    'developers_guide_js.html#Interactive_Samples';

// Constant Labels.
jsguide.LOGIN_LABEL = 'Login';
jsguide.LOGOUT_LABEL = 'Logout';
jsguide.RUNCODE_LABEL = 'Run';
jsguide.RUNNING_LABEL = 'running ...';
jsguide.LOADING_LABEL = 'loading ...';
jsguide.CODELINK_LABEL = 'download source code';
jsguide.APP_NAME = 'GoogleInc-calendar_jsguide-1.0';
jsguide.SAMPLE_DIR = '/svn/trunk/calendar/samples/';
jsguide.SAMPLE_OVERVIEW_EXTENSION = '.html';
jsguide.SAMPLE_SOURCECODE_EXTENSION = '.js';

/**
 * Global initalization.  
 * - Hides page content if this is a token redirection.
 * - Redirect the main URL if this page isn't accessed through an iframe.
 * - Initiate UI generation process.
 */   
jsguide.main = function() {  

  if (jsguide.isTokenRedirect()){   
    jQuery('body').css({visibility: 'hidden'});
  } else {
    if (!jsguide.isIframe()) {
      window.location.href = jsguide.PARENT_URL; 
    }

    // Initialize the calendar service object.
    jsguide.calendarService =
        new google.gdata.calendar.CalendarService(jsguide.APP_NAME);
        
    // Begin UI initialization.
    setTimeout(jsguide.initUI, 0);
  }
};

/**
 * UI initialization.
 * - Start the DOM generation process.
 * - Attach event handlers to UI components for AuthSub login/logout.
 */
jsguide.initUI = function() {

  // Begin DOM generate.
  jsguide.generateCode();

  // set up event handlers for calendar login/logout buttons.
  jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).click(function() {
    if (!jsguide.hasToken(jsguide.SCOPE_URL)) {
      google.accounts.user.login();
    } else {
      jsguide.setRunEnabled(false);
      google.accounts.user.logout();
      jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).
        attr({value: jsguide.LOGIN_LABEL});

      var re = new RegExp(['_', jsguide.RUNCODE_BUTTON].join(''));

      jQuery(jsguide.getId(jsguide.TAB_SECTION)).
          find('input').each(function() {
              if (re.test(jQuery(this).attr('id'))) {
                jQuery(this).get(0).disabled = false;
                jQuery(this).attr({'value': jsguide.LOGIN_LABEL});
              }
          });
    }
  });  

  // Initialize display label and runnable status based on login status.
  if (jsguide.hasToken(jsguide.SCOPE_URL)) {
    jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).
        attr({value: jsguide.LOGOUT_LABEL});
    jsguide.setRunEnabled(true);
  } else {
    jQuery(jsguide.LOGIN_BUTTON).
        attr({value: jsguide.LOGIN_LABEL});
    jsguide.setRunEnabled(false);
  }
}

/**
 * Check if this script is being executed inside an iframe.
 * @return {boolean} True/false to indicate if there is a calendar AuthSub. 
 */
jsguide.isIframe = function() {
  
  var status = false; 

  try {
    // Attempt to access parent parent (if there is one).
    status = !(parent.location.href == window.location.href);
  } catch (exception) {
    // A cross domain parent access cause an exception, so now 
    // we know this page is being loaded as an iframe.
    status = true;
  }
  return status;
}

/**
 * Check if there is a token valid for the input scope URL.
 * @param {string} scope The Scope URL.
 * @return {boolean} True/false to indicate if there is a valid token for this
 *     scope URL.
 */  
jsguide.hasToken = function(scope) {
  var success = true;
  if (google.accounts.user.checkLogin(scope) === '') {
    success = false;
  }
  return success;
};

/**
 * Detect whether the current session is a token redirect
 * @return {Boolean} True/false to whether this is a redirect session
 */  
jsguide.isTokenRedirect = function() {

  var status = false;
  
  var url = location.href;

  var matchArr = url.match(/#2/);
  
  if (matchArr != null) {
    status = true;
  }

  return status;
};

/*
 * Render all buttons within each tab section to be enabled or disabled.
 * @param {boolean} enabled The boolean flag to indicate enabld or disabled.
 */
jsguide.setRunEnabled = function(enabled) {

  var re = new RegExp(['_', jsguide.RUNCODE_BUTTON].join(''));

  jQuery(jsguide.getId(jsguide.TAB_SECTION)).
      find('input').each(function() {

    if (!re.test(jQuery(this).attr('id'))) {
      jQuery(this).get(0).disabled = !enabled;
    }
  });
}

/*
 * The callback hook that is exposed to Codepress engine so that 
 * when the Codepress element is fully loaded it will call this method
 * to indicate that it is now ready for source code loading.
 * @param {string} actionId The action ID of a tab section.
 */
jsguide.initContent = function(actionId) {
  var overviewFileName = actionId + jsguide.SAMPLE_OVERVIEW_EXTENSION;
  var sourceFileName = actionId + jsguide.SAMPLE_SOURCECODE_EXTENSION;
  jsguide.setOverviewFile(overviewFileName, 
      jsguide.getId(jsguide.OVERVIEW, actionId));
  jsguide.setSourceFile(sourceFileName,
      [actionId, '_', jsguide.SOURCECODE].join(''));  
}

/*
 * This method takes the tabtemplate DOM structure and automatically 
 * generate copies of it to represent each action links listed within  
 * the action div.  Each newly generated tab section is subsequently
 * initialized with event handlers for all its feature buttons.
 */
jsguide.generateCode = function() {     

  // Grab all links within the action div section.
  jQuery([jsguide.getId(jsguide.ACTION_SECTION),' a'].
      join('')).each(function() {
    
    // Each actionLink represents a use case which will generate a 
    // whole new tab section based on the tabtemplate tree
    var actionLink = jQuery(this);

    // Store its ID as its actionId.
    var actionId = actionLink.attr('id');

    // Store the action link text.
    var actionText = actionLink.html();

    // Grab the tabtemplate div and clone everything in it.
    var clonedTemplate = jQuery(jsguide.getId(jsguide.TAB_TEMPLATE)).clone();

    // Rename the ID of this clone to be prepended with the actionId.
    clonedTemplate.attr({'id': [actionId, '_', jsguide.TAB].join('')});

    // Rename all IDs of every descendent element inside the 
    // cloned template to be prepended with the actionId.
    clonedTemplate.find('*[id]').each(function() { 
      var descendent = jQuery(this);
      var newId = [actionId, '_', descendent.attr('id')].join('');
      descendent.attr({'id': newId});  
    });  
   
    // Point the action link to this newly cloned template section.
    actionLink.attr({href: jsguide.getId(jsguide.ANCHOR, actionId)}); 

    // Locate the source code link element to place the source code link.
    codeLinkId = jsguide.getId(jsguide.CODELINK, actionId);

    // Form the URL to the source code.
    sourceUrl = [jsguide.SAMPLE_DIR, actionId,
        jsguide.SAMPLE_SOURCECODE_EXTENSION].join('');

    // Form the URL to the souce code in HTML format.
    sourceUrlHtml = ['<a target="_blank" href="', sourceUrl, '">', 
        jsguide.CODELINK_LABEL, '</a>'].join('');

    // Add the source code link to the code link element
    clonedTemplate.find(codeLinkId).html(sourceUrlHtml);

    // Grab the place holder (tabsection) where all these generated 
    // cloned templates will go into
    var tabsection = jQuery(jsguide.getId(jsguide.TAB_SECTION));   
    
    // Create an anchor link at the beginning of each cloned template
    var anchor = jQuery('<a/>');
    anchor.attr({name: [actionId, '_', jsguide.ANCHOR].join('')});
    tabsection.append(anchor);
    tabsection.append(clonedTemplate);
    
    // Set the output tab of this cloned template with the action text.
    jQuery(jsguide.getId(jsguide.ACTION_TEXT, actionId)).html(actionText);

    // if there is no auth token, replace the "Run" label with "Login" 
    if (!jsguide.hasToken(jsguide.SCOPE_URL)) {
      jQuery(jsguide.getId(jsguide.RUNCODE_BUTTON, actionId)).attr(
      {'value': jsguide.LOGIN_LABEL});
    }

    // Set up the click handler of the runcode button of this cloned template
    jQuery(jsguide.getId(jsguide.RUNCODE_BUTTON, actionId)).click(function() {
      
      // When this run button is really a login (no token is found)
      if (!jsguide.hasToken(jsguide.SCOPE_URL)) {
        jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).trigger('click');
        return;
      }

      // When this button, this action case is the current action
      jsguide.currentActionId = actionId;

      // Between each run session of the code, the display should be cleared
      jsguide.clearDisplay();  

      PRINT(jsguide.RUNNING_LABEL);

      // Dynamically retrieve the current CodePress object
      var currentCodePress = 
          window[[actionId, '_', jsguide.SOURCECODE].join('')];
      
      // Retrieve the current CodePress object source content, and execute it
      try {
        eval(currentCodePress.getCode());
      } catch(e) {
        PRINT(['JavaScript error(s): ', e.message].join(''));
      }
      // Go to the output tab for this cloned template to wait for output
      jQuery([jsguide.getId(jsguide.TAB, actionId), ' > ul'].join('')).
          tabsClick(2);
    });
    
    // Set up the resetcode button.
    jQuery(jsguide.getId(jsguide.RESETCODE_BUTTON, actionId)).
        click(function() {
      var fileName = actionId + jsguide.SAMPLE_SOURCECODE_EXTENSION;
      jsguide.setSourceFile(fileName, 
          [actionId, '_', jsguide.SOURCECODE].join(''));
    });
    
    // Set up the reruncode button.
    jQuery(jsguide.getId(jsguide.RERUNCODE_BUTTON, actionId)).
        click(function() {
      jQuery(jsguide.getId(jsguide.RUNCODE_BUTTON, actionId)).
          trigger('click');
    });
    
    // Set up the editcode button.
    jQuery(jsguide.getId(jsguide.EDITCODE_BUTTON, actionId)).
        click(function() {
      jQuery([jsguide.getId(jsguide.TAB, actionId), ' > ul'].join('')).
          tabsClick(1);
    });

    // Set up the viewcalendar button.
    jQuery(jsguide.getId(jsguide.VIEWCALENDAR, actionId)).click(function() {
      jsguide.showCalendar(actionId);      
    });

    // This is specific to the jQuery UI Tabs plugin. Basically, all the href 
    // of within the targeted UL will be mapped to a div section.  So This is 
    // rewriting all the links inside UI to point to the right anchor link 
    // (start with #).
    jQuery([jsguide.getId(jsguide.TAB, actionId), ' > ul'].join('')).
        find('*[href]').each(function() {
      var href = jQuery(this).attr('href');
      var re = /#([a-zA-Z0-9]+)$/;
      href.match(re);
      var href = RegExp.$1;
      jQuery(this).attr({'href': ['#', actionId, '_', href].join('')});      
    });

    // Now this cloned template is finished rewriting all its IDs and links,
    // ready for the Tabs plugin initialization.  No specific actions are
    // associated with tab behaviors, so use empty functions.
    jQuery([jsguide.getId(jsguide.TAB, actionId), ' > ul'].join('')).tabs({
      click: function() {},
      hide: function() {},
      show: function() {}
    });
    
  });  
 
  CodePress.run();

  // Let's initialize the embeddable calendar chooser.
  jsguide.initCalendarChooser();
};

/*
 * AJAX fetch the content specified by the file name and set the content of
 * the DIV ID with this content.
 * @param {string} fileName The name of the file to be fetched with AJAX.
 * @param {string} divId The DIV ID where its HTML content will be set with the
 *     file's content.
 */
jsguide.setOverviewFile = function(fileName, divId) {
  
  // Indicate the file is currently being loaded.
  jQuery(divId).html(jsguide.LOADING_LABEL);
  
  // The file is residing in the samples directory of the same host of 
  // this script.
  var fileUrl = jsguide.SAMPLE_DIR + fileName;

  jQuery.ajax({      
      url: fileUrl, 
      success: 
          function(text){
            // On success put the content of the file in the DIV
            jQuery(divId).html(text);
          },
      cache: false
      });
};

/*
 * AJAX fetch the source code specified by the file name and set the CodePress 
 * object with the source code.
 * @param {string} fileName The name of the file to be fetched with AJAX.
 * @param {string} codepressName The name of the CodePress object (mapped to an
 *     action case.
 */
jsguide.setSourceFile = function(fileName, codepressName) {

  var cpObject = window[codepressName];
  
  if (cpObject) {
    cpObject.setCode(jsguide.LOADING_LABEL);
  }
 
  var sourceFileUrl = jsguide.SAMPLE_DIR + fileName;


  jQuery.ajax({
    url: sourceFileUrl,
    cache: false,
    success: function(text){
      // Just before loading the source code to the CodePress, try to estimate 
      // and set the height of the sourcecode textarea based on the fetched 
      // source code.  This way, the height of the textarea will be just fitted
      // for the length of the source code.
      var newLineCount = 0;
      for (var i = 0; i < text.length; i++) {
        var c = text.charAt(i);
        if (c == '\n') {
          newLineCount++;
        }
      }         
      cpObject.style.height = newLineCount * jsguide.PX_PER_LINE;   
      cpObject.setCode(text);
      cpObject.editor.syntaxHighlight('init');        
    }
  });
};

/**
 * Initialize the embeddable calendar chooser with an onchange handler.
 */  
jsguide.initCalendarChooser = function() {  
  var calChooser = jQuery(jsguide.getId(jsguide.CALENDARCHOOSER));  
  calChooser.change(function() {
    // When the select chooser is changed, the ID of the selected option will 
    // be used as the calendar ID to refresh the embeddable calendar.
    var id = $(this).get(0).options[$(this).get(0).selectedIndex].id;
    jQuery(jsguide.getId(jsguide.EMBED_CALENDAR)).
        attr({src: [jsguide.EMBED_CALENDAR_URL, id].join('')});
  });  
}

/**
 * This method fetches list of calendar entries of the authenticated user 
 * using the allcalendars feed.  Use this list to replace the items in the 
 * embeddable calendar chooser, and immediate trigger a 'change' event to 
 * refresh the calendar.  Move the embeddable calendar chooser to the current
 * action case.
 * @param {string} actionId The current tab section which the action is 
 *     taken place.
 */  
jsguide.showCalendar = function(actionId) { 

  var setCalendarChooser = function(result) {
    
    // Empty the current embeddable calendar chooser list
    var calChooser = jQuery(jsguide.getId(jsguide.CALENDARCHOOSER));  
    calChooser.empty();
    
    // populate the embeddable calendar chooser list with the 
    // latest entries from allcalendars feed
    var entries = result.feed.entry;
    for (var i = 0; i < entries.length; i++) {
      var title = entries[i].getTitle().getText();
      var id =
          entries[i].getId().getValue().
          replace([jsguide.ALL_CALENDAR_FEED, '/'].join(''), '');
      var option = jQuery('<option/>');
      option.attr({id: id});
      option.html(title);
      calChooser.append(option);
    }
    
    // Trigger an event 'change', so the embeddable calendar would be refreshed
    calChooser.trigger('change');
    
    // Move the embeddable calendar to the current action section
    var calSection = jQuery(jsguide.getId(jsguide.CALENDAR_SECTION));
    calSection.appendTo(jsguide.getId(jsguide.EMBED_CALENDAR_HOLDER, 
        actionId));
    calSection.css({display: 'block'});
  }

  // Use the calendar service to request the allcalendars feed.
  jsguide.calendarService.getOwnCalendarsFeed(
      jsguide.ALL_CALENDAR_FEED, setCalendarChooser, jsguide.handleError);
};

/**
 * Check if there is a calendar AuthSub token for the current session
 * @return {Boolean} True/false to indicate if this is a token redirect
 */  
jsguide.handleError = function(e) {
  var error = e.cause ? e.cause.statusText : e.message;
  
  jQuery(jsguide.getId(jsguide.getId(jsguide.DISPLAY), 
      jsguide.currentActionId)).html(error);
  
  if (jsguide.hasToken(jsguide.SCOPE_URL)) {
    jsguide.setRunEnabled(false);
    google.accounts.user.logout();
    jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).
        attr({value: jsguide.LOGIN_LABEL});
  }
};

/*
 * This method return the actual CSS ID of an element. Since a lot of 
 * the DOM elements are auto-generated, prepended the actionId to form the 
 * actual CSS ID, if one is available.
 * @param {string} elementId The CSS ID of an element.
 * @param {string} actionId The action case this element live within.
 * @return {string} The actual CSS ID that points to an element within 
 *     this page.
 */
jsguide.getId = function(elementId, actionId) {
  var actualId = ['#', elementId].join('');
  if (actionId) {
    actualId = ['#', actionId, '_', elementId].join('');
  }
  return actualId;
}

/**
 * Clear the display area of the current action section
 */  
jsguide.clearDisplay = function() {
  var display = jQuery(jsguide.getId(jsguide.DISPLAY, 
      jsguide.currentActionId));
  display.empty();
};

/*
 * Convenient method for the user of the dev guide to use within the
 * sample code to print out the given text in the display area of the output
 * tab.
 * @param {string} Some text.
 */
var PRINT = function(text) {

  var display = jQuery(jsguide.getId(jsguide.DISPLAY, 
      jsguide.currentActionId));
  
  var re = /running \.\.\./

  if (re.test(display.html())) {
    display.empty();
  }
  
  display.append(text);
  display.append('<br/>');
};

// Load the Google data JavaScript client library
google.load('gdata', '1.x');

// Set the callback function when the library is ready
google.setOnLoadCallback(jsguide.main);
